/*
 * R5.c
 *      Created Nov 04, 2013
 *              Authors: Alex Bouvy, Joseph DeLong, Justin Hamric, Alex Newcomer
 */

#include <dos.h>
#include "mpx_supt.h"

//CONSTANTS
#define COM_CLOSED 0
#define COM_OPEN 1
#define COM_IDLE -1
#define READING 2
#define WRITING 3
#define PIC_MASK 0x21
#define PIC_CMD 0x20
#define EOI 0x20
#define EVENT_FLAG_CLEAR 0
#define EVENT_FLAG_SET 1
#define COM_INT_ID 0x0C
#define COM_BASE 0x3F8
#define COM_INT_EN COM_BASE+1
#define COM_BRD_LSB COM_BASE
#define COM_BRD_MSB COM_BASE+1
#define COM_INT_ID_REG COM_BASE+2
#define COM_LC COM_BASE+3
#define COM_MC COM_BASE+4
#define COM_LS COM_BASE+5
#define COM_MS COM_BASE+6
#define DEVICE_CLS 0
#define DEVICE_IDLE -1
#define DEVICE_READ 2
#define DEVICE_WRITE 3
#define DEVICE_OPEN 1

//COM_OPEN
#define ERR_COP_INVFLG (-101) /* invalid event flag pointah */
#define ERR_COP_INVBRD (-102) /* invalid baud rate divisah */
#define ERR_COP_PRTOPEN (-103) /* port already open-ah */

//COM_CLOSE
#define ERR_CCL_PNOP (-201) /* serial port not open-ah */

//COM_READ
#define ERR_CRD_PNOP (-301) /* port not open (PNOP) */
#define ERR_CRD_INVBUFAD (-302) /* invalid buffah address */
#define ERR_CRD_INVCNT (-303) /* invalid count */
#define ERR_CRD_DVCBSY (-304) /* device busy-ah */

//COM_WRITE
#define ERR_CWR_PNOP (-401) /* serial port-ah not open-ah */
#define ERR_CWR_INVBUFAD (-402) /* invalid buffah address */
#define ERR_CWR_INVCNTAD (-403) /* invalid count address-ah or value */
#define ERR_CWR_DVCBSY (-404) /* device-ah bus-ah */

//PROTOTYPES
int com_open(int *eflag_p, int baud_rate);
int com_close(void);
int com_read(char *buf_p, int *count_p);
int com_write(char *buf_p, int *count_p);
void interrupt handlerBase();
void handlerRead();
void handlerWrite();

//DATA STRUCTURES
/*
 * Device Control Block Structure
 *       stores information about the device
 *
 *      Needed variables:
 *       Flag indicating whether the device is open or closed
 *       Pointer to the event flag associated with the device. Should be of type (int *).
 *       Status code- possible values are COM_IDLE, READING, WRITING.
 *       Addresses and counters associated with the requestors input buffer. For example:
 *              char* in_buff; - pointer the requestors buffer (where read data is will be placed)
 *              int *in_count; - counter of the maximum number of chars that can be placed in the buffer
 *              int in_done; -counter for how many characters have been placed in the requestors buffer
 *       Addresses and counters associated with the requestors output buffer
 *              Same as above, but separate variables needed for output
 *       An array to be used as the ring buffer, with the associated input index, output index,
 *              and counter.
 *              char[size] ring_buffer;
 *              int ring_buffer_in; -index where you will write the next character to the ring buffer
 *              int ring_buffer_out; -index where you will remove the next character from the buffer
 *              int ring_buffer_count; -count of how many character stored but not read from the buffer
 */
struct DCB {
	int openFlag; //1 is open 0 is closed
	int* eventFlag;
	int status;  //COM_IDLE 0, READING 1, or WRITING 2
	char* in_buff; //pointer to buffer
	int* in_count; //counter of max number of characters that can be placed in buffer
	int in_done; //counter for how many characters have been placed in the requestor's buffer
	char* out_buff;
	int* out_count; //counter of max number of characters that can be placed in buffer
	int out_done; //counter for how many characters have been removed from requestor's buffer
	char ring_buffer[255];
	int ring_buffer_in; //where you will write the next character to the ring buffer
	int ring_buffer_out; //where you will remove the next character from the buffer
	int ring_buffer_count; //how many characters stored but not yet read from buffer
};

struct DCB *com;

void interrupt (*sys_int_handler) ();

/*
 * int com_open(int *eflag_p, int baud_rate);
 *      Parameters
 *              eflag_p: pointer to an integer event flag within the calling program
 *              baud_rate: integer value representing the desired baud rate
 *      Returns (you may want to define symbolic constants for these return
 *              values)
 *              0 if no error
 *              -101 if invalid even flag pointer
 *              -102 if invalid baud rate divisor
 *              -103 if port already open
 *      Generally, this routine will:
 *              Initialize the DCB
 *              Set the interrupt vector to the new handler address
 *              Compute and store the baud rate divisor
 *              Set other characteristics
 *              Enable all of the necessary interrupts
 */
int com_open(int *eflag_p, int baud_rate) {
	int baud_rate_divisor;
	int mask;
	com = (struct DCB*)sys_alloc_mem(sizeof(struct DCB));
	if(eflag_p == NULL){//event flag must be null
		return ERR_COP_INVFLG;
	}
	if(baud_rate < 1){//check baud rate validity
		return ERR_COP_INVBRD;
	}
	if(com->openFlag == 1){//if the device is open
		return ERR_COP_PRTOPEN;
	}
	//set DCB values before opening
	com->openFlag = 1;
	com->eventFlag = eflag_p;
	com->status = COM_IDLE;
	com->ring_buffer_in = 0;
	com->ring_buffer_out = 0;
	com->ring_buffer_count = 0;
	//set interrupt handler
	sys_int_handler = getvect(COM_INT_ID);
	setvect(COM_INT_ID, &handlerBase);
	//get baud rate divisor
	baud_rate_divisor = 115200/(long)baud_rate;
	//setup line characteristics
	outportb(COM_LC, 0x80);
	outportb(COM_BRD_LSB,(baud_rate_divisor));// & 0xFF));
	outportb(COM_BRD_MSB,((baud_rate_divisor >> 8)));//&0xFF));
	outportb(COM_LC,0x03);
	disable();
	//setup PIC
	mask = inportb(PIC_MASK);
	mask = mask&~0x10;
	outportb(PIC_MASK, mask);
	enable();
	//enable port interrupts
	outportb(COM_MC, 0x08);
	outportb(COM_INT_EN, 0x01);
	return 0;
}

/*
 * int com_close(void);
 * Returns
 *      0 if no error
 *      -201 if serial port not open
 */
int com_close(void) {
	int mask;
	if(com->openFlag != COM_OPEN) { //port not open
		return ERR_CCL_PNOP;
	}
	//close port
	com->openFlag = COM_CLOSED;
	disable();
	mask = inportb(PIC_MASK);
	mask = mask|0x10;
	outportb(PIC_MASK, mask);
	enable();
	//disable interrupts
	outportb(COM_MS, 0x00);
	outportb(COM_INT_EN, 0x00);
	//return to old int vector
	setvect(COM_INT_ID, 0x00);
	//destroy old com
	sys_free_mem(com);
	return 0; //no error
}

/*
 * int com_read(char * buf_p, int *count_p);
 *      Parameters
 *              buf_p: far pointer to the starting address of the buffer to
 *                      receive the input characters
 *              count_p: address of an integer count value indicating the
 *                      number of characters to be read
 *      Returns
 *              0 if no error
 *              -301 if port not open
 *              -302 if invalid buffer address
 *              -303 if invalid count address or count value
 *              -304 if device is busy
 */
int com_read(char *buf_p, int *count_p) {
	if(com->openFlag == DEVICE_CLS){//if device not open
		return ERR_CRD_PNOP;
	}
	if(buf_p == NULL) { //invalid buffer address
		return ERR_CRD_INVBUFAD;
	}
	if(count_p == NULL) { //invalid count
		return ERR_CRD_INVCNT;
	}
	if(com->status != COM_IDLE ){
		return ERR_CRD_DVCBSY;
	}
	com->status = READING;
	com->in_buff = buf_p;
	com->in_count = count_p;
	com->in_done = 0;
	*com->eventFlag = 0; //INDICATE THAT WE ARE DOING STUFF
	disable();
	com->openFlag = READING;
	while((com->ring_buffer_count > 0) && (com->ring_buffer[com->ring_buffer_in] != '\r') && (com->in_done < *(com->in_count))) { //while ring buffer contains characters that are not a '/r'
		com->in_buff[com->in_done] = com->ring_buffer[com->ring_buffer_in]; //copy from ring buffer to DCB buffer
		if(com->ring_buffer_in > 255){//what's our ring buff size?
			com->ring_buffer_in = 0;
		}
		com->ring_buffer_count--;
		com->in_done++;
		com->ring_buffer_in++;
	}
	enable();
	if(com->in_done < *(com->in_count)) { //if more characters are required by device
		com->status = COM_IDLE;
		return 0;//success
	}
	com->in_buff[com->in_done - 1] = '/0'; //null terminate the input buffer
	com->openFlag = COM_IDLE;
	*(com->eventFlag) = 1; //INDICATE THAT WE ARE ALL DONE DOING STUFF
	*count_p = com->in_done;
	com->status = COM_IDLE;
	return 0; //ALL WENT WELL
}

/*
 * int com_write(char * buf_p, int *count_p);
 *      Parameters
 *              buf_p: pointer to the starting address of the buffer
 *                      containing the block of characters to be written
 *              count_p: address of the integer count value indicating the
 *                      number of characters to be written
 *      Return
 *              0 if no error
 *              -401 if serial port not open
 *              -402 is invalid buffer address
 *              -403 if invalid count address or count value
 *              -404 if device busy
 */
int com_write(char *buf_p, int *count_p) {
	unsigned char mask;
	//check input
	if(com->openFlag == DEVICE_CLS){//if device not open
		return ERR_CWR_PNOP;
	}
	if(count_p == NULL){
		return ERR_CWR_INVCNTAD;
	}
	if(buf_p == NULL){
		return ERR_CWR_INVBUFAD;
	}
	if(com->status != COM_IDLE ){
		return ERR_CWR_DVCBSY;
	}
	com->status = WRITING;
	com->out_buff = buf_p;//install buffer in dcb
	com->out_count = count_p;//install num of chars to be written
	com->out_done = 0;//reset out done
	com->openFlag = DEVICE_WRITE;//designate write in progress
	*com->eventFlag = 0;//operation in progress!
	outportb(COM_BASE, *com->out_buff);//write first character
	com->out_buff++;
	com->out_done++;
	//disable();//protect us from interrupts
	mask = inportb(COM_INT_EN);//grab int enable register
	mask = mask|0x02;//set interrupt enable register so our handler can catch it.
	outportb(COM_INT_EN, mask);//set int enable register
	//enable();//enable interrupts
	//event flag = 1 NO, this is handled in our handler (heh)
	com->status = COM_IDLE;
	return 0;
}

//FIRST LEVEL INTERRUPT HANDLER
/*
 * This interrupt handler will be called when an
 *      I/O interrupt on the com port is generated. It
 *      will determine the cause of the interrupt (either
 *      a read or a write), and will call the appropriate
 *      second level interrupt handler
 */
void interrupt handlerBase() {
	int op_val;
	if(com->openFlag == 0){ //port is closed
		disable();
		outportb(PIC_CMD, EOI);
		enable();
	}
	else{
		disable();
		op_val = inportb(COM_INT_ID_REG);
		op_val = op_val & 0x07;
		enable();
		if(op_val == 0x02){
			handlerWrite();
		}
		else if(op_val == 0x04){
			handlerRead();
		}
		outportb(PIC_CMD, EOI);
	}
	return;
}

//Second Level Interrupt- Read/Input:
/*
 * This interrupt is called when a read interrupt is
 *      generated (this means a single character has been
 *      placed in the input register). If the current status
 *      of the device is READING, this means that a
 *      process is currently requesting data from the com
 *      port- therefore you will place the character in the
 *      requestors input buffer. If the status is not
 *      READING, no READ operation is currently
 *      underway, so you should store the character in the
 *      ring buffer.
 */
void handlerRead() {
	unsigned char input;
	input = inportb(COM_BASE);//read from port
	if(com->openFlag != DEVICE_READ){
		//no process reading data!
		if(com->ring_buffer_count >= 255){
			return; //discard the character? what
		} else {  
			com->ring_buffer[com->ring_buffer_in++] = input; //write to ring buffer	
			com->ring_buffer_count++;
			if(com->ring_buffer_in > 255){
				com->ring_buffer_in = 0;	//goin in circles yo
			}
		}
	} else {//current status is read
		com->in_buff[com->in_done++] = input;
		if(input == '\r' || com->in_done == *com->in_count){
			com->in_buff[com->in_done-1] = '\0';
			com->openFlag = COM_IDLE;
			*(com->eventFlag) = EVENT_FLAG_SET;
			*(com->in_count) = com->in_done;
		}
	}
}

//Second Level Interrupt- Write/Output:
/*
 * This interrupt is generated after a character has
 *      been successfully written to the com port. It will
 *      check if the output buffer in the DCB has been
 *      completely copied. If it has, it will perform some
 *      cleanup operations and indicate that the operation
 *      is complete. If there is still data in the buffer that
 *      needs to be copied, it will copy the next character
 *      to the output register, which will generate another
 *      interrupt, which will cause this procedure to be
 *      repeated (and thus the next character written)
 *      until all data has been written.
 */
void handlerWrite() {
	int mask;
	//if current status is not writing, ignore interrupt and return
	if(com->status != DEVICE_WRITE){
		//do nothing.
	}
	else{
		if(com->out_done == *(com->out_count)){//contents of buffer has been emptied
			//clear the interrupt, reset status to idle, set event flag to indicate completion of operation
			//disable write interrupits by clearing bit 1 in the interrupt enable register
			disable();
			outportb(PIC_CMD, EOI);
			com->status = IDLE;
			*(com->eventFlag) = EVENT_FLAG_SET;
			mask = inportb(COM_INT_EN);
			mask = mask & 0xFD; //clear bit 1
			outportb(COM_INT_EN, mask);
			enable();
		}
		else{
			//copy next character to the output register and increment the appropriate counts
			disable();
			outportb(COM_BASE, *com->out_buff);
			com->out_buff++;
			com->out_done++;
			enable();
		}
	}
	return;
}
